---
title: Babel plugin
description: How Unistyles babel plugin works?
---

import { Card, Aside } from '@astrojs/starlight/components'
import Seo from '../../../../components/Seo.astro'

<Seo
    seo={{
        title: 'Unistyles Babel plugin',
        description: 'Learn how Unistyles babel plugin works'
    }}
>


Unistyles 3.0 relies heavily on the Babel plugin, which helps convert your code in a way that allows binding the `ShadowNode` with `Unistyles`. Before reading this guide, make sure to check the [Look under the hood](/v3/start/how-unistyles-works) guide.

Our golden rule is to never introduce any component that could pollute your native view hierarchy. In other words, if you use a `View`, it will be rendered as-is in the native view hierarchy.

Let's discuss the responsibilities of the Babel plugin:

### 1. Detecting StyleSheet dependencies

Each `StyleSheet` is different. One might rely on a `theme`, another on `miniRuntime`, and so on.
The same applies to `styles`. Each style depends on different things. For example, you can wrap your app in a `View` that safeguards your app from rendering behind the notch or navigation bar.
Another style might be used in your `Typography` component and provides text color based on the apps' theme.

Should the `Typography` style re-calculate on an `insets` change? Or should the `View` that relies on insets re-render on a theme change?

We don't think that's a good idea. The first responsibility of the Babel plugin is to detect all dependencies in your `StyleSheet`. This ensures that only the relevant styles are recalculated when necessary.

```ts
// Babel: depends on theme
const stylesheet = StyleSheet.create(theme => ({
    container: {
        // Babel: depends on theme
        backgroundColor: theme.colors.background
    },
    text: {
        // Babel: static (no dependencies)
        fontSize: 12
    }
}))
```

```ts
// Babel: depends on theme and miniRuntime
const stylesheet = StyleSheet.create((theme, rt) => ({
    container: {
        // Babel: depends on theme and insets
        paddingTop: rt.insets.top,
        paddingBottom: rt.insets.bottom,
        backgroundColor: theme.colors.background
    },
    text: (fontSize: number) => ({
        // Babel: depends on theme
        color: theme.colors.text,
        // Babel: depends on fontScale
        fontSize: rt.fontScale >= 3
            ? fontSize * 1.5
            : fontSize * 0.8
    })
}))
```

<Aside type="caution" title="Dependency detection limitation">
We put a lot of effort into making dependency detection as accurate as possible, thus we support:
- destructuring of `theme` and `rt` objects
- style's as functions / arrow functions / objects
- nested ifs and other conditionals
- ternary operators
- logical operators
- binary operators
- and much more...

**What we don't support**:
- moving functions/arrow functions out of StyleSheet.create
- `theme` and `rt` reassignment to other variables (we don't track them)
</Aside>


### 2. Attaching unique id to each StyleSheet

This helps us identify your `StyleSheet` while you're developing your app and trigger multiple `hot-reloads`. Such identification is required to swap your `StyleSheet` with another one, ensuring that you get up-to-date values during reloads.
This feature does not affect your app in production, as the bundle never reloads in that environment.

### 3. Component factory (borrowing ref)

This is the most crucial part—without it, Unistyles won’t be able to update your views from C++.
In the early versions of Unistyles 3.0, we tried solving this problem by using the `ref` prop, but it wasn’t reliable enough.
Many developers use different style syntaxes, making it impossible to support all of them.

Instead, we decided to leave the user’s `ref` as is and transfer the implementation from Babel to our component factory.
This way we have more control and we have an unified way of registering your `ShadowNodes`.

The component factory is a function that takes your component and renders it with an overridden `ref` prop:

```ts
const factory = Component => <Component ref={someMagic✨} {...props} />;
```

<Aside>
We override your `ref` prop to connect a `ShadowNode` to the attached Unistyle(s).
From the runtime perspective, your component will render the same way as before!
We only borrow your `ref` momentarily to update the ShadowRegistry.

We’re also React 19–ready and will call your `ref` cleanup function if it’s present.
</Aside>


Let’s go through some examples so you can better understand how this works:

```ts title="Your code"
import { View } from 'react-native'

const ref = useRef()

<View ref={ref} />
```

```ts title="Babel transform"
import { View } from 'react-native-unistyles/src/components/native/View'

const ref = useRef()

// no changes
<View ref={ref} />
```

We also support other components to extract `ShadowNode` from them:

```ts title="Your code"
import { Pressable, Image } from 'react-native'

<Pressable
    ref={ref => {
        doSomething(ref)
    }}
    onPress={() => {}}
/>
<Image source={require('./image.png')} style={styles.image} ref={ref2} />
```

```ts title="Babel transform"
import { Pressable } from 'react-native-unistyles/components/native/Pressable'
import { Image } from 'react-native-unistyles/components/native/Image'

// no changes
<Pressable
    ref={ref => {
        doSomething(ref)
    }}
    onPress={() => {}}
/>
<Image source={require('./image.png')} style={styles.image} ref={ref2} />
```

### 4. Creating scopes for stateless variants

When you use variants, each time you call `useVariants`, a new scope is created. This scope contains a local copy of stylesheet that won't affect other components.
This feature is similar to time travel, allowing you to explore different states of your styles with different calls to `useVariants`.

From your perspective, using variants is simple: you just need to call the `useVariants` hook:

```tsx
styles.useVariants({
    size: 'small'
})
```

Behind the scenes, we create a scoped constant that can be accessed anywhere within the same block:

```tsx
const _styles = styles
{
    const styles = _styles.useVariants({
        size: 'small'
    })

    // Your code here
}
```

This approach also works seamlessly with `console.log`, allowing you to inspect styles at any point:

```tsx
// Styles without variants
console.log(styles.container)

styles.useVariants({
    size: 'small'
})

// Styles with variants: small
console.log(styles.container)

styles.useVariants({
    size: 'large'
})

// Styles with variants: large
console.log(styles.container)
```

By leveraging such scopes, we ensure support for any level of nesting!

### Extra configuration

The Babel plugin comes with a few additional options to extend its usage.

:::caution
By default babel plugin will look for any `react-native-unistyles` import to start processing your file.
You can change this behaviour with options below:
:::

### `root` (required)

All files within the specified root folder will be processed by the Babel plugin.
If you need to process extra folders, use with `autoProcessPaths` option.

```js
{
    root: 'src' // or 'app', or any name of your root folder
}
```

Folder name will be resolved with `process.cwd()`.

<Aside type="note">
If you use monorepo or a project with Expo Router, you most likely have multiple root folders.

In that case:
- set `root` to the folder containing your components eg. `@myapp/ui` or `components`
- use the `autoProcessImports` option to whitelist your folder (check example below)
</Aside>

### `autoProcessImports`

This configuration should be used when you want to process files containing specific imports.
It can be useful for monorepos that use Unistyles with absolute paths, such as `@codemask/styles`.

```js
{
    autoProcessImports: ['@codemask/styles'] // whenever Babel encounters this import, it will process your file
}
```

### `autoRemapImports`

This is the most powerful option, but most likely, you won't need to use it.
It allows you to remap uncommon imports to Unistyles components.

This may happen if a 3rd library does not import `react-native` components directly, but instead uses its own factory or a relative path.
Unistyles uses it internally to support the following imports from `react-native` internals:

```js
import { NativeText } from "react-native/Libraries/Text/TextNativeComponent"
import View from "react-native/Libraries/Components/View/ViewNativeComponent"
```

Let's say you have a library called `custom-library` that imports `react-native` raw components directly:

```js title="node_modules/custom-library/components/index.js"
import { NativeText } from "react-native/Libraries/Text/TextNativeComponent"
import View from "react-native/Libraries/Components/View/ViewNativeComponent"
```

To convert it to Unistyles, you can use the following configuration:

```ts
{
    autoRemapImports: [
        path: 'node_modules/custom-library/components', // <- must be path from node_modules
        imports: [
            {
                isDefault: false, // <- is default import?
                name: 'NativeText', // <- if not, what's the import name?
                path: 'react-native/Libraries/Text/TextNativeComponent' // <- what's the import source?
                mapTo: 'NativeText' // <- which Unistyles component should be used? Check react-native-unistyles/src/components/native
            },
            {
                isDefault: true,
                path: 'react-native/Libraries/Components/View/ViewNativeComponent',
                mapTo: 'NativeView'
            }
        ]
    ]
}
```

:::caution
If you use raw `react-native` imports within your code, Unistyles will auto map it to `react-native-unistyles` factories.
This option should only be used for 3rd party libraries from `node_modules`.
:::

### `autoProcessPaths`

This configuration is unrelated to the `root`, `autoProcessImports`, and `autoRemapImports` options and can be used alongside them.
By default, the Babel plugin ignores `node_modules`. However, you can extend these paths to attempt converting 3rd components into Unistyles compatible ones.
Within these paths, we will replace `react-native` imports with `react-native-unistyles` factories that borrow component refs. [Read more](/v3/other/babel-plugin#3-component-factory-borrowing-ref).

Defaults to:

```ts
['react-native-reanimated/src/component']
```

### `debug`

In order to list detected dependencies by the Babel plugin you can enable the `debug` flag.
It will `console.log` name of the file and component with Unistyles dependencies.

### Usage with React Compiler

Check [this guide](/v3/guides/react-compiler) for more details.

#### Usage in `babel.config.js`

You can apply any of the options above as follows:

```js title="babel.config.js"
/** @type {import('react-native-unistyles/plugin').UnistylesPluginOptions} */
const unistylesPluginOptions = {
    // any component in this folder will be processed
    root: 'src',
    // also files with these imports will be processed (in any non-root folder)
    autoProcessImports: ['@react-native-ui-kit', '@codemask/styles'],
    // additionally process components from this `node_modules` package
    autoProcessPaths: ['external-library/components'],
    // log what you've found
    debug: true,
}

module.exports = function (api) {
    api.cache(true)

    return {
        // other config
        plugins: [
            ['react-native-unistyles/plugin', unistylesPluginOptions]
            // other plugins
        ]
    }
}
```

</Seo>
